#include "sprite.h"
#include "debug.h"
#include <stdio.h>
#include <png.h>
#define PNGSIGSIZE 8

static void userReadData(png_structp pngPtr, png_bytep data, png_size_t length);

int validate(FILE *file) {

    //Allocate a buffer of 8 bytes, where we can put the file signature.
    png_byte pngsig[PNGSIGSIZE];


    //Read the 8 bytes from the stream into the sig buffer.
    if(fread(pngsig, 1, PNGSIGSIZE, file) != 8){
        dbg_msg("ERROR: png file does not have proper png sig!\n");
        return 0;
    }

    //Let LibPNG check the sig. If this function returns 0, everything is OK.
    return (png_sig_cmp(pngsig, 0, PNGSIGSIZE) == 0);
}

int load_png(const char *filename, sprite_t *spr){
    //so First, we validate our stream with the validate function I just mentioned
    FILE *file = fopen(filename, "r");

    if(file == NULL){
        dbg_msga("ERROR: Unable to open '%s'\n", filename);
        return 0;
    }

    if (!validate(file)) {
        dbg_msga("ERROR:'%s' Data is not valid PNG-data\n", filename);
        return 0;
    }

    //Here we create the png read struct. The 3 NULL's at the end can be used
    //for your own custom error handling functions, but we'll just use the default.
    //if the function fails, NULL is returned. Always check the return values!
    png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!pngPtr) {
        dbg_msg("ERROR: Couldn't initialize png read struct\n");
        return 0;
    }

    //Here we create the png info struct.
    //Note that this time, if this function fails, we have to clean up the read struct!
    png_infop infoPtr = png_create_info_struct(pngPtr);
    if (!infoPtr) {
        dbg_msg("ERROR: Couldn't initialize png info struct\n");
        png_destroy_read_struct(&pngPtr, (png_infopp)0, (png_infopp)0);
        return 0;
    }
    //Here I've defined 2 pointers up front, so I can use them in error handling.
    //I will explain these 2 later. Just making sure these get deleted on error.
    png_bytep* rowPtrs = NULL;
    char* data = NULL;

    if (setjmp(png_jmpbuf(pngPtr))) {
        //An error occured, so clean up what we have allocated so far...
        png_destroy_read_struct(&pngPtr, &infoPtr,(png_infopp)0);
        if (rowPtrs != NULL) {free(rowPtrs);}
        if (data != NULL) {free(data);}

        dbg_msg("ERROR: An error occured while reading the PNG file\n");

        //Make sure you return here. libPNG will jump to here if something
        //goes wrong, and if you continue with your normal code, you might
        //End up with an infinite loop.
        return 0 ; // Do your own error handling here.
    }
    png_set_read_fn(pngPtr,(voidp)file, userReadData);
    //Set the amount signature bytes we've already read:
    //We've defined PNGSIGSIZE as 8;
    png_set_sig_bytes(pngPtr, PNGSIGSIZE);
    //Now call png_read_info with our pngPtr as image handle, and infoPtr to receive the file info.
    png_read_info(pngPtr, infoPtr);
#ifdef OPENGL
    spr->tex.texw = spr->loc.w =  png_get_image_width(pngPtr, infoPtr);
    spr->tex.texh = spr->loc.h = png_get_image_height(pngPtr, infoPtr);
#endif

    //bits per CHANNEL! note: not per pixel!
    png_uint_32 bitdepth   = png_get_bit_depth(pngPtr, infoPtr);
    //Number of channels
    png_uint_32 channels   = png_get_channels(pngPtr, infoPtr);
    //Color type. (RGB, RGBA, Luminance, luminance alpha... palette... etc)
    png_uint_32 color_type = png_get_color_type(pngPtr, infoPtr);
    dbg_msga("(%u x %u) %u-depth, %u channels\n", spr->loc.w, spr->loc.h, (unsigned int)bitdepth, (unsigned int)channels);

    if(color_type != PNG_COLOR_TYPE_RGBA){
        dbg_msg("Only RGBA pngs are supported.  Please change your"
                " png type.  (Try using GIMP!)\n");
        return 0;
    }
    
    //Here's one of the pointers we've defined in the error handler section:
    //Array of row pointers. One for every row.
    //rowPtrs = new png_bytep[imgHeight];
    rowPtrs = malloc(spr->loc.h * sizeof(png_bytep));

    //Alocate a buffer with enough space.
    //(Don't use the stack, these blocks get big easilly)
    //This pointer was also defined in the error handling section, so we can clean it up on error.
    //data = new char[imgWidth * imgHeight * bitdepth * channels / 8];
    data = malloc(spr->loc.w * spr->loc.h * bitdepth * channels / 8);
    //This is the length in bytes, of one row.
    const unsigned int stride = spr->loc.w * bitdepth * channels / 8;

    //A little for-loop here to set all the row pointers to the starting
    //Adresses for every row in the buffer
    for (size_t i = 0; i < spr->loc.h; i++) {
        //Set the pointer to the data pointer + i times the row stride.
        //Notice that the row order is reversed with q.
        //This is how at least OpenGL expects it,
        //and how many other image loaders present the data.
        //pUInt q = (imgHeight- i - 1) * stride;
        unsigned int q = (spr->loc.h- i - 1) * stride;
        rowPtrs[i] = (png_bytep)data + q;
    }

    //And here it is! The actuall reading of the image!
    //Read the imagedata and write it to the adresses pointed to
    //by rowptrs (in other words: our image databuffer)
    png_read_image(pngPtr, rowPtrs);

    bind_sprite(spr, data);

    free(rowPtrs);
    free(data);

    png_destroy_read_struct(&pngPtr, &infoPtr,(png_infopp)0);
    return 1;
}

static void userReadData(png_structp pngPtr, png_bytep data, png_size_t length) {
    //Here we get our IO pointer back from the read struct.
    //This is the parameter we passed to the png_set_read_fn() function.
    //Our std::istream pointer.
    png_voidp a = png_get_io_ptr(pngPtr);
    //Cast the pointer to std::istream* and read 'length' bytes into 'data'
    fread(data, 1, length, (FILE*)a);
    //((std::istream*)a)->read((char*)data, length);
}
